讓我們從一個問題開始:
// 這個函式無法編譯
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
編譯器會報錯:
error[E0106]: missing lifetime specifier
help: this function's return type contains a borrowed value,
but the signature does not say whether it is borrowed from `x` or `y`
問題是:編譯器不知道返回的參考是來自 x
還是 y
,因此無法確保返回的參考在使用時仍然有效。
生命週期註解不會改變參考的實際生命週期,它只是告訴編譯器參考之間的關係:
// 'a 讀作 "lifetime a"
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
這告訴編譯器:
x
和 y
的生命週期至少要和 'a
一樣長'a
'a
是 x
和 y
生命週期中較短的那個fn main() {
let string1 = String::from("長字串");
{
let string2 = String::from("短");
let result = longest(string1.as_str(), string2.as_str());
println!("最長的字串是: {}", result);
} // string2 在這裡被丟棄,result 也不能在這之後使用
// println!("{}", result); // 錯誤!result 的生命週期已經結束
}
視覺化生命週期:
string1 的生命週期: |-------------------------------------|
string2 的生命週期: |----------|
result 的生命週期: |---------| (受限於較短的 string2)
&i32 // 一個參考
&'a i32 // 帶有明確生命週期的參考
&'a mut i32 // 帶有明確生命週期的可變參考
// 單一生命週期參數
fn single_lifetime<'a>(x: &'a str) -> &'a str {
x
}
// 多個生命週期參數
fn multiple_lifetimes<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
x // 只返回 x,所以返回值的生命週期是 'a
}
// 不同的生命週期關係
fn complex_lifetimes<'a, 'b: 'a>(x: &'a str, y: &'b str) -> &'a str {
// 'b: 'a 表示 'b 的生命週期至少要和 'a 一樣長
if x.len() > 0 {
x
} else {
y // 因為 'b >= 'a,所以可以返回 y
}
}
當結構體包含參考時,必須指定生命週期:
#[derive(Debug)]
struct TaskRef<'a> {
title: &'a str,
description: &'a str,
}
impl<'a> TaskRef<'a> {
fn new(title: &'a str, description: &'a str) -> Self {
TaskRef { title, description }
}
fn get_title(&self) -> &str {
self.title
}
}
fn main() {
let title = String::from("學習生命週期");
let desc = String::from("理解 Rust 的生命週期機制");
let task = TaskRef::new(&title, &desc);
println!("任務: {:?}", task);
// title 和 desc 必須活得比 task 久
}
struct TaskManager<'a> {
name: String,
current_task: Option<&'a Task>,
}
impl<'a> TaskManager<'a> {
fn new(name: String) -> Self {
TaskManager {
name,
current_task: None,
}
}
fn set_current_task(&mut self, task: &'a Task) {
self.current_task = Some(task);
}
fn describe_current(&self) {
match self.current_task {
Some(task) => println!("當前任務: {}", task.title),
None => println!("沒有當前任務"),
}
}
}
fn main() {
let task = Task::new(
String::from("完成專案"),
String::from("在截止日期前完成")
);
let mut manager = TaskManager::new(String::from("專案經理"));
manager.set_current_task(&task);
manager.describe_current();
// task 必須活得比 manager 久(或一樣久)
}
Rust 有三個生命週期省略規則,讓你在大多數情況下不需要明確寫出生命週期:
// 你寫的
fn foo(x: &str, y: &str) -> String
// 編譯器看到的
fn foo<'a, 'b>(x: &'a str, y: &'b str) -> String
// 你寫的
fn foo(x: &str) -> &str
// 編譯器看到的
fn foo<'a>(x: &'a str) -> &'a str
// 你寫的
impl Task {
fn get_title(&self) -> &str
}
// 編譯器看到的
impl Task {
fn get_title<'a>(&'a self) -> &'a str
}
'static
是一個特殊的生命週期,表示參考在整個程式執行期間都有效:
// 字串字面值都有 'static 生命週期
let s: &'static str = "我是靜態字串";
// 定義全域常數
static GLOBAL_TASK: &str = "全域任務";
// 函式可以要求 'static 生命週期
fn requires_static(s: &'static str) {
println!("靜態字串: {}", s);
}
fn main() {
requires_static("這是字串字面值");
requires_static(GLOBAL_TASK);
let dynamic = String::from("動態字串");
// requires_static(&dynamic); // 錯誤!dynamic 不是 'static
}
有時我們需要指定泛型的生命週期界限:
use std::fmt::Display;
// T 必須實作 Display 且生命週期至少和 'a 一樣長
fn longest_with_announcement<'a, T>(
x: &'a str,
y: &'a str,
ann: T,
) -> &'a str
where
T: Display,
{
println!("公告: {}", ann);
if x.len() > y.len() {
x
} else {
y
}
}
struct Context<'a> {
text: &'a str,
}
struct Parser<'a, 'b> {
context: &'a Context<'b>,
}
impl<'a, 'b> Parser<'a, 'b> {
fn parse(&self) -> Vec<&'b str> {
self.context.text.split(',').collect()
}
}
fn main() {
let text = String::from("Rust,生命週期,借用");
let context = Context { text: &text };
let parser = Parser { context: &context };
let result = parser.parse();
println!("解析結果: {:?}", result);
}
fn first_or_second<'a>(first: &'a str, second: &'a str, use_first: bool) -> &'a str {
if use_first {
first
} else {
second
}
}
impl<'a> TaskRef<'a> {
// 返回的生命週期與 self 相同
fn title(&self) -> &str {
self.title
}
}
struct TaskIterator<'a> {
tasks: &'a [Task],
index: usize,
}
impl<'a> Iterator for TaskIterator<'a> {
type Item = &'a Task;
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.tasks.len() {
let task = &self.tasks[self.index];
self.index += 1;
Some(task)
} else {
None
}
}
}
記住這個重要概念:
fn demonstrate_relationship() {
let owned = String::from("我擁有這個"); // 所有權
{
let borrowed = &owned; // 借用開始,生命週期 'a 開始
println!("{}", borrowed);
} // 借用結束,生命週期 'a 結束
drop(owned); // 所有權結束,值被清理
}
當遇到生命週期錯誤時,問自己這些問題:
// 常見錯誤:返回局部變數的參考
fn bad_function() -> &String {
let s = String::from("局部變數");
&s // 錯誤!s 會被丟棄
}
// 解決方案 1:返回擁有的值
fn good_function_1() -> String {
String::from("擁有的值")
}
// 解決方案 2:接收參考並返回它
fn good_function_2(s: &str) -> &str {
s
}
// 修復這個函式
fn combine_strings(s1: &str, s2: &str) -> &str {
let result = String::from(s1) + s2;
&result // 這裡有問題!
}
struct Cache<'a> {
data: Option<&'a str>,
}
impl<'a> Cache<'a> {
fn new() -> Self {
// 實作
}
fn set(&mut self, value: &'a str) {
// 實作
}
fn get(&self) -> Option<&str> {
// 實作
}
}
struct StrSplitter<'a> {
remainder: Option<&'a str>,
delimiter: &'a str,
}
impl<'a> StrSplitter<'a> {
fn new(string: &'a str, delimiter: &'a str) -> Self {
// 實作
}
}
impl<'a> Iterator for StrSplitter<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
// 實作分割邏輯
}
}